package org.mtmi.ui.controls;

import java.util.ArrayList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.mtmi.tuio.listeners.TUIODragEvent;
import org.mtmi.tuio.listeners.TUIOTapEvent;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.M;
import org.mtmi.ui.drawables.IDrawable;
import org.mtmi.ui.drawables.SymbolDrawable;
import org.mtmi.ui.layouts.LayoutData;
import org.mtmi.ui.scenes.CLR;
import org.mtmi.ui.scenes.COLOR;
import org.mtmi.ui.scenes.Scene;

/**
 * A button made to be drawn on scene side.
 *
 */
public class SideButton extends AbstractButton
{
	private static final int MG = 4; //margin between side and button
	protected static final int ER = 50; //external radius of button
	private static final int CD = MG+ER+ER; //corner catch distance
	
	
	private boolean can_move_button;
	private boolean can_remove_button;
	
	/**
	 * @param scene
	 * @param flags Use M.LEFT, RIGHT, TOP, BOTTOM to set side where button will be drawn.
	 */
	public SideButton( Scene scene, int flags ) {
		super(scene, flags);
		setForeground( COLOR.yellow );
		setBackground( CLR.darker( getForeground(), 0.5f ) );
		setSize( computeSize( null ) );
		updateLocation();
		can_move_button = true;
		can_remove_button = false; 
		forbidden_area = false;
	}
	
	/** @return the side where this button is sticked on */
	public int getSideLocation() { return getFlags() & M.DIRECTION_MASK; }
	//note: there are no setSideLocation() because it sometimes require setBounds() also (not in corner)
	
	/** @return true is the button can be moved using drag gesture */
	public boolean isCanMoveButton() { return can_move_button; }
	/** change the move button policy */
	public void setCanMoveButton( boolean m ) { can_move_button=m; }
	
	/** @return true is the button can be removed using drag-to-center gesture */
	public boolean isCanRemoveButton() { return can_remove_button; }
	/** change the remove button policy */
	public void setCanRemoveButton( boolean m ) { can_remove_button=m; }

	protected boolean isMoving() { return moved; }
	
	/** update location of button from its flags */
	private void updateLocation()
	{
		int f = getFlags();
		boolean top = (f & M.TOP) != 0;
		boolean btm = (f & M.BOTTOM) != 0;
		boolean lft = (f & M.LEFT) != 0;
		boolean rgt = (f & M.RIGHT) != 0;
		
		Point ss = getScene().getSize();
		int x, y ;
		if( top ) {
			y = MG;
		} else if( btm ) {
			y = ss.y-MG-ER;
		} else {
			y =ss.y/2-ER; //by default at center
		}
		if( lft ) {
			x = MG;
		} else if( rgt ) {
			x = ss.x-MG-ER;
		} else {
			x = ss.x/2-ER;//by default at center
		}
		setLocation( x, y );
	}
	
	@Override
	public Point computeSize(LayoutData l) {
		int f = getFlags();
		boolean top = (f & M.TOP) != 0;
		boolean btm = (f & M.BOTTOM) != 0;
		boolean lft = (f & M.LEFT) != 0;
		boolean rgt = (f & M.RIGHT) != 0;
		
		boolean corner = top && ( lft || rgt ) || btm && (lft || rgt );
		if( corner ) {
			return new Point( ER, ER );
		} else {
			if( top || btm ) {
				return new Point( 2*ER, ER );
			} else {
				return new Point( ER, 2*ER );
			}
		}
	}

	@Override
	public boolean containsTouch(TouchControlEvent e) {
		int f = getFlags();
		boolean top = (f & M.TOP) != 0;
		boolean btm = (f & M.BOTTOM) != 0;
		boolean lft = (f & M.LEFT) != 0;
		boolean rgt = (f & M.RIGHT) != 0;
		
		if( !GraphicUtil.rectContains( location, size, e.x, e.y ) ) return false;
		
		//check for circular area.
		if( top ) {
			if( lft ) {
				return GraphicUtil.circleContains( location.x, location.y, ER, e.x, e.y );
			}
			else if( rgt ) {
				return GraphicUtil.circleContains( location.x+ER, location.y, ER, e.x, e.y );
			}
			else {
				return GraphicUtil.circleContains( location.x+ER, location.y, ER, e.x, e.y );
			}
		}
		else if( btm ) {
			if( lft ) {
				return GraphicUtil.circleContains( location.x, location.y+ER, ER, e.x, e.y );
			}
			else if( rgt ) {
				return GraphicUtil.circleContains( location.x+ER, location.y+ER,  ER, e.x, e.y );
			}
			else {
				return GraphicUtil.circleContains( location.x+ER, location.y+ER, ER, e.x, e.y );
			}
		}
		//left or right side
		else if( lft ) {
			return GraphicUtil.circleContains( location.x, location.y+ER, ER, e.x, e.y );
		}
		else if( rgt ) {
			return GraphicUtil.circleContains( location.x+ER, location.y+ER, ER, e.x, e.y );
		}
		return false;
	}

	/** 
	 *  draw Side button in given rectangle, flags are TOP,LEFT,RIGHT,BOTTOM,
	 *  using current gc's foreground and background.
	 *  @param er external radius.
	 */
	public static void paint( GC gc, int x, int y, int er, int flags ) 
	{
		boolean top = (flags & M.TOP) != 0;
		boolean btm = (flags & M.BOTTOM) != 0;
		boolean lft = (flags & M.LEFT) != 0;
		boolean rgt = (flags & M.RIGHT) != 0;
		
		int ir = er/5;
		int er2 = 2*er;
		int ir2 = 2*ir;
		
		Path p = new Path( gc.getDevice() );
		if( top ) {
			if( lft ) {
				p.addArc( x, y, ir2, ir2, 180, -90 );
				p.addArc( x-er, y-er, er2, er2, 0, -90 );
			}
			else if( rgt ) {
				p.addArc( x, y-er, er2, er2, 270, -90 );
				p.addArc( x+er-ir2, y, ir2, ir2, 90, -90 );
			}
			else {
				p.addArc( x, y-er, er2, er2, 0, -180 );
			}
		}
		else if( btm ) {
			if( lft ) {
				p.addArc( x-er, y, er2, er2, 90, -90 );
				p.addArc( x, y+er-ir2, ir2, ir2, 270, -90 );
			}
			else if( rgt ) {
				p.addArc( x, y, er2, er2, 180, -90 );
				p.addArc( x+er-ir2, y+er-ir2, ir2, ir2, 0, -90 );
			}
			else {
				p.addArc( x, y, er2, er2, 180, -180 );
			}
		}
		//left or right side
		else if( lft ) {
			p.addArc( x-er, y, er2, er2, 90, -180 );
		}
		else if( rgt ) {
			p.addArc( x, y, er2, er2, 270, -180 );
		}
		
		p.close();
		gc.setAlpha( 180 );
		gc.fillPath( p );
		gc.setAlpha( 255 );
		int a = gc.getAntialias();
		gc.setAntialias( SWT.ON );
		gc.drawPath( p );
		gc.setAntialias( a );
	}
	
	@Override
	public void paint( GC gc )
	{
		if( will_remove ) {			
			gc.setForeground( COLOR.red );
			gc.setBackground( CLR.darker( COLOR.red, 0.5f ) );
		} else {
			gc.setForeground( getForeground() );
			gc.setBackground( getBackground() );
		}
		gc.setLineWidth( 3 );
		paint( gc, location.x, location.y, ER, getFlags() );
		
		//draw red cross over button if user will remove it:
		if( will_remove || forbidden_area ) {
			SymbolDrawable d = new SymbolDrawable( COLOR.red, SymbolDrawable.CROSS );
			Rectangle r = getRemoveSymbolBounds();
			d.paint( gc, r.x, r.y, r.width, r.height, IDrawable.NORMAL );
		}
	}

	private int dgdx, dgdy;
	private boolean moved,will_remove,forbidden_area;
	private int save_flags;
	private Rectangle save_bounds;
	
	@Override
	public void tapDown( TUIOTapEvent event ) {
		save_flags = getFlags();
		save_bounds = getBounds(null);
		dgdx = location.x-event.ex;
		dgdy = location.y-event.ey;
		moved = false;
		forbidden_area = false;
		super.tapDown( event );
	}
	
	@Override
	public void tapUp( TUIOTapEvent event ) {
		if( will_remove ) {
			redrawRemoveSymbol();
			dispose();
		}
		else if( forbidden_area ) { 
			redrawRemoveSymbol();
			//replace button at start location
			redraw();
			flags = save_flags;
			setBounds( save_bounds );
			redraw();
			forbidden_area = false;
		}
		if( !moved ) {
			//avoid fire button selected if user drag it to other location.
			super.tapUp(event);
		}
		moved = false;
	}
	
	/** mark the current remove symbol area to be redrawn */
	private void redrawRemoveSymbol() {
		Rectangle r = getRemoveSymbolBounds();
		GraphicUtil.expandRect( r, 3 );
		getScene().redraw( r.x, r.y, r.width, r.height, true );
	}
	
	@Override
	public void drag( TUIODragEvent event ) {
		if( can_move_button ) {
			if( will_remove || forbidden_area ) {
				//mark remove symbol area to be redraw (may change location, or removed)
				redrawRemoveSymbol();
			}
			will_remove = false;
			forbidden_area = false;
			moved = true;
			int nf = 0;

			Point ss = getScene().getSize();

			if( event.ex < CD ) {
				nf |= M.LEFT;
			} else if( event.ex > ss.x-CD ) {
				nf |= M.RIGHT;
			}
			if( event.ey < CD ) {
				nf |= M.TOP;
			} else if( event.ey > ss.y-CD ) {
				nf |= M.BOTTOM;
			}

			if( nf==0 ) {
				//not enough close to scene side, but can choose side using min distance to side
				//there are also remove are for button that can be removed:
				if( can_remove_button ) {
					if( GraphicUtil.rectContains( getRemoveArea(), event.ex, event.ey ) ) {
						redraw();
						location.x = dgdx + event.ex;
						location.y = dgdy + event.ey;
						will_remove=true;
						redraw();
						//mark red cross location to be redrawn also:
						redrawRemoveSymbol();
						return ;
					}
				}
				int ld = event.ex;
				int rd = ss.x-event.ex;
				int td = event.ey;
				int bd = ss.y-event.ey;
				int d = ld;
				nf = M.LEFT;
				if( rd < d ) {
					d = rd;
					nf = M.RIGHT;
				}
				if( td < d ) {
					d = td;
					nf = M.TOP;
				}
				if( bd < d ) {
					d = bd;
					nf = M.BOTTOM;
				}
//TODO: check if there are another button under this one				
			}
			else {
				//check if there are another button at this corner!
				for( SideButton b : getSideButtons( scene ) ) {
					if( b!=this && b.getSideLocation()==nf ) {
						forbidden_area = true;
						//mark red cross location to be redrawn also:
						redrawRemoveSymbol();
						break;
					}
				}
			}

			int cf = getFlags() & M.DIRECTION_MASK;

			int lx = dgdx + event.ex;
			int ly = dgdy + event.ey;

			flags = (flags & ~M.DIRECTION_MASK) | nf;

			redraw(); //mark current bounds to be redrawn
			
			if( nf == (M.TOP|M.LEFT) ) {
				if( cf!=nf ) {
					location.x = MG;
					location.y = MG;
					updateLocation();
					dgdx = location.x-event.ex;
					dgdy = location.y-event.ey;
					setSize( computeSize(null) );
				}
			}
			else if( nf == (M.TOP|M.RIGHT) ) {
				if( cf!=nf ) {
					location.x = ss.x-MG-ER;
					location.y = MG;
					updateLocation();
					dgdx = location.x-event.ex;
					dgdy = location.y-event.ey;
					setSize( computeSize(null) );
				}
			}
			else if( nf == (M.BOTTOM|M.LEFT) ) {
				if( cf!=nf ) {
					location.x = MG;
					location.y = ss.y-MG-ER;
					updateLocation();
					dgdx = location.x-event.ex;
					dgdy = location.y-event.ey;
					setSize( computeSize(null) );
				}
			}
			else if( nf == (M.BOTTOM|M.RIGHT) ) {
				if( cf!=nf ) {
					location.x = ss.x-MG-ER;
					location.y = ss.y-MG-ER;
					updateLocation();
					dgdx = location.x-event.ex;
					dgdy = location.y-event.ey;
					setSize( computeSize(null) );
				}
			}
			else if( nf == M.TOP ) {
				updateLocation();
				setSize( computeSize(null) );
				location.x = lx;//-ER;
				//dgdx = ER;//location.x-event.ex;
				//dgdy = location.y-event.ey;
			}
			else if( nf == M.BOTTOM ) {
				updateLocation();
				setSize( computeSize(null) );
				location.x = lx;//-ER;
				//dgdx = ER;//location.x-event.ex;
				//dgdy = location.y-event.ey;
			}
			else if( nf == M.LEFT ) {
				updateLocation();
				setSize( computeSize(null) );
				location.y = ly;//-ER;
			//	dgdx = location.x-event.ex;
			//	dgdy = ER;//location.y-event.ey;
			}
			else if( nf == M.RIGHT ) {
				updateLocation();
				setSize( computeSize(null) );
				location.y = ly;//-ER;
				//dgdx = location.x-event.ex;
				//dgdy = ER;//location.y-event.ey;
			}

			redraw(); //redraw at new location
		}
		else { //cannot move button using drag gesture:
			super.drag( event );
		}
	}
	
	@Override
	public Rectangle getRedrawArea( Rectangle r ) {
		r = getBounds( r );
		//add border width:
		GraphicUtil.expandRect( r, borderWidth );
		return r;
	}
	
	/** @return the remove area used to drag button into to remove it */ 
	private Rectangle getRemoveArea() {
		Point sz = getScene().getSize();
		//by choice this is the center of scene:
		return new Rectangle( sz.x/3, sz.y/3, sz.x/3, sz.y/3 );
	}
	
	/** @return the area where remove symbol will be drawn (might be outside button bounds */
	private Rectangle getRemoveSymbolBounds()
	{
		Point ss = getScene().getSize();
		//if button is near remove area, place cross over it
		if( location.x > ER && location.x < ss.x-ER && location.y> ER && location.y<ss.y-ER ) {
			return new Rectangle( location.x+(size.x-48)/2, location.y-50, 48, 48 );
		}
		else {
			//otherwise depends on bounds:
			int d = 48;
			int f = getFlags();
			if( (f & M.TOP)==M.TOP ) {
				if( (f & M.LEFT)==M.LEFT ) {
					return new Rectangle( location.x+size.x, location.y+size.y, d, d );
				}
				else if( (f & M.RIGHT)==M.RIGHT ) {
					return new Rectangle( location.x-d, location.y+size.y, d, d );
				}
				else {
					return new Rectangle( location.x+(size.x-d)/2, location.y+size.y, d, d );
				}
			}
			else if( (f & M.BOTTOM)==M.BOTTOM ) {
				if( (f & M.LEFT)==M.LEFT ) {
					return new Rectangle( location.x+size.x, location.y-d, d, d );
				}
				else if( (f & M.RIGHT)==M.RIGHT ) {
					return new Rectangle( location.x-d, location.y-d, d, d );
				}
				else {
					return new Rectangle( location.x+(size.x-d)/2, location.y-d, d, d );
				}
			}
			else {
				if( (f & M.LEFT)==M.LEFT ) {
					return new Rectangle( location.x+size.x, location.y+(size.y-d)/2, d, d );
				}
				else if( (f & M.RIGHT)==M.RIGHT ) {
					return new Rectangle( location.x-d, location.y+(size.y-d)/2, d, d );
				}
			}
		}
		return new Rectangle( 0,0,100,100);
	}
	
	/** @return the list of SideButton defined on this scene */
	public static ArrayList<SideButton> getSideButtons( Scene scene )
	{
		ArrayList<SideButton> sbs = new ArrayList<SideButton>();
		for( Control c : scene.getControls( true ) ) {
			if( c instanceof SideButton ) {
				sbs.add( (SideButton)c );
			}
		}
		for( Control c : scene.getControls( false ) ) {
			if( c instanceof SideButton ) {
				sbs.add( (SideButton)c );
			}
		}
		return sbs;
	}
}
